﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.IO;

namespace LINQToolKit.Mapping
{
    public sealed class XmlMappingSource : IMappingSource
    {
        IDictionary<Type, MetaEntity> _entities;

        public XmlMappingSource()
        {
            this._entities = new Dictionary<Type, MetaEntity>();
        }

        public static XmlMappingSource Default
        {
            get
            {
                string path = Path.Combine(
                    AppDomain.CurrentDomain.BaseDirectory, "mappings.config");
                return XmlMappingSource.Load(path);    
            }
        }

        private static XmlMappingSource Load(XElement doc)
        {
            var source = new XmlMappingSource();
            var entities =
                from es in doc.Elements("entity")
                select new MetaEntity
                {
                    Type = es.Attribute("type").ToType(true),
                    SourceUri = es.Attribute("sourceUri").GetString(),
                    PrimaryKey = es.Attribute("primaryKey").GetString(),
                    EntryUri = es.Attribute("entryUri").GetString(),
                    InsertUri = es.Attribute("insertUri").GetString(),
                    UpdateUri = es.Attribute("updateUri").GetString(),
                    DeleteUri = es.Attribute("deleteUri").GetString(),
                    IsInsertable = es.Attribute("isInsertable").GetBoolean(),
                    IsDeleteable = es.Attribute("isDeleteable").GetBoolean(),
                    IsUpdateable = es.Attribute("isUpdatable").GetBoolean(),
                    Processor = es.Attribute("processor").ToType(true),
                    Properties =
                        from p in es.Elements("property")
                        select new MetaProperty
                        {
                            Name = p.Attribute("name").GetString(),
                            IsCollection = p.Attribute("isCollection").GetBoolean(),
                            Path = p.Attribute("path").GetString(),
                            Processor = p.Attribute("processor").ToType(true)
                        },
                    Associations =
                        from ass in es.Elements("association")
                        select new MetaAssociation 
                        {
                            Name = ass.Attribute("name").GetString(),
                            Key = ass.Attribute("key").GetString(),
                            Source = ass.Attribute("source").GetString(),
                        },
                    Namespaces = 
                        from ns in es.Elements("namespace")
                        select new MetaNamespace
                        {
                            Prefix = ns.Attribute("prefix").GetString(),
                            Uri = ns.Attribute("uri").GetString()
                        }
                };

            var namespaces =
                from ns in doc.Elements("namespace")
                select new MetaNamespace
                {
                    Prefix = ns.Attribute("prefix").GetString(),
                    Uri = ns.Attribute("uri").GetString()
                };

            foreach (var item in entities)
            {
                item.Namespaces = item.Namespaces.Concat(namespaces);
                source.Entities.Add(item.Type, item);
            }

            // to bind the entities with the mapping source
            source.BindMapping();
            return source;
        }

        public static XmlMappingSource Load(string path)
        {
            path.AssertNull("path");

            if (! File.Exists(path))
            {
                throw new FileNotFoundException(path);
            }

            return XmlMappingSource.Load(XElement.Load(path));
        }

        public static XmlMappingSource LoadFromXml(string xml)
        {
            xml.AssertNull("xml");

            XElement doc = XElement.Parse(xml);
            return XmlMappingSource.Load(doc);
        }

        public IDictionary<Type, MetaEntity> Entities
        {
            get { return this._entities; }
            set { this._entities = value; }
        }

        /// <summary>
        /// validate the mapping source
        /// </summary>
        private void BindMapping()
        {
            // avoid the outter trap of deferred execution
            foreach (var entity in this.Entities.Values)
            {
                entity.MappingSource = this;
                entity.Properties = entity.Properties.ToList();
                entity.Associations = entity.Associations.ToList();

                foreach (var property in entity.Properties)
                {
                    property.Entity = entity;
                }

                foreach (var ass in entity.Associations)
                {
                    ass.Entity = entity;
                }
            }
        }


        public void Validate()
        {
            // do nothing
        }
    }
}